using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

namespace Gazebo.Core
{
    public abstract class PreferenceContainer
    {
        private MemberMapping[] maps;
        
        protected PreferenceContainer()
        {
            BuildMap();
            Reload();
        }
        
        private void BuildMap()
        {
            List<MemberMapping> list = new List<MemberMapping>();
            Type type = GetType();
            
            foreach (MemberInfo info in type.GetMembers(BindingFlags.Instance |
                                                        BindingFlags.Public |
                                                        BindingFlags.NonPublic |
                                                        BindingFlags.FlattenHierarchy)) {
                PreferenceAttribute attribute = info.GetCustomAttribute<PreferenceAttribute>(false);
                if (attribute == null || !info.IsDataMember()) { 
                    continue;
                }
                
                MemberMapping mapping = new MemberMapping();
                mapping.Attribute = attribute;
                mapping.MemberInfo = info;
                list.Add(mapping);
            }
            
            maps = list.ToArray();
        }
        
        protected void Reload()
        {
            XElement root = GetContainerRoot();
            
            foreach (MemberMapping map in maps) {
                XElement dataElement = root.Element(map.Attribute.ElementName);
                
                bool success = false;
                
                if (dataElement != null) {
                    try {
	                    string data = dataElement.Value;
                        Type convertType = map.MemberInfo.GetDataMemberType();
	                    object convertedData = Convert.ChangeType(data, convertType);
	                    
	                    map.MemberInfo.SetDataMember(this, convertedData);
                        
                        success = true;
                    } catch {
                        // Do nothing.  The success variable will not be set if
                        // an exception is thrown and we handle that below.
                    }
                }
                
                if (!success) {
                    map.MemberInfo.SetDataMember(this, map.Attribute.DefaultValue);
                }
            }
            
            OnLoadCompleted();
        }
        
        public void Save()
        {
            XElement root = GetContainerRoot();
            root.RemoveNodes();
            
            OnSave();
            
            foreach (MemberMapping map in maps) {
                object data = map.MemberInfo.GetDataMember(this);
                object def = map.Attribute.DefaultValue;
                
                bool isDefault = false;
                
                if (data != null) {
                    isDefault = data.Equals(def);
                }
                
                if (isDefault) {
                    continue;
                }
                
                XElement dataElement = new XElement(map.Attribute.ElementName, data.ToString());
                
                root.Add(dataElement);
            }
        }
        
        protected abstract XElement GetContainerRoot();
        
        protected virtual void OnLoadCompleted()
        {
        }
        
        protected virtual void OnSave()
        {
        }
        
        private struct MemberMapping {
            public PreferenceAttribute Attribute;
            public MemberInfo MemberInfo;
        }
    }
}
